package org.weso.lodin.appserv;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.weso.lodin.analyzer.CountryReconciliator;
import org.weso.lodin.dao.CountryDAO;
import org.weso.lodin.dao.ObservationDAO;
import org.weso.lodin.datasource.config.DatasetTemplateDAO;
import org.weso.lodin.generated.Dataset;
import org.weso.lodin.generated.IdIndicator;
import org.weso.lodin.generated.TypeTable;
import org.weso.lodin.generated.TypeValues;
import org.weso.lodin.to.CompleteModelTO;
import org.weso.lodin.to.CountryTO;
import org.weso.lodin.to.DatasetTO;
import org.weso.lodin.to.ObservationTO;
import org.weso.lodin.to.RankingScoreObservationTO;
import org.weso.lodin.utils.MD5Utils;
import org.weso.lodin.utils.PrefixManager;
import org.weso.lodin.utils.StatusUtils;

import com.hp.hpl.jena.rdf.model.AnonId;
import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.vocabulary.DC;
import com.hp.hpl.jena.vocabulary.DCTerms;
import com.hp.hpl.jena.vocabulary.RDF;
import com.hp.hpl.jena.vocabulary.RDFS;

public class TransformerAppServ {


	private static final String WESO_ACRONYM = "WESO";
	private static final String MD5_SEPARATOR = "#";
	public static final String LANG_EN = "en";
	public static final String WEBINDEX_RAW_DATA_XLS = "excel/RawData-final-2.xls";	
	public static final String WEBINDEX_RAW_DATA_2_XLS = "excel/RawData-final-20120813-wf.xls";
	public static final String COUNTRIES_COUNTRIES_FAO_XLS = "countries/countries-fao.xls";
	public static final String HTTP_PURL_ORG_LINKED_DATA_CUBE = PrefixManager.getURIPrefix("qb");
	public static final String HTTP_PURL_ORG_LINKED_DATA_SDMX_2009_ATTRIBUTE = PrefixManager.getURIPrefix("sdmx-attribute");
	public static final String HTTP_PURL_ORG_LINKED_DATA_SDMX_2009_CONCEPT = PrefixManager.getURIPrefix("sdmx-concept");
	public static final String HTTP_PURL_ORG_LINKED_DATA_SDMX_2009_CODE = PrefixManager.getURIPrefix("sdmx-code");
	public static final String WEBINDEX_BASE_URI = PrefixManager.getURIPrefix("wi");
	public static final String LAYER_BASE_URI = PrefixManager.getURIPrefix("wi-layer");	
	public static final String COMPONENT_BASE_URI = PrefixManager.getURIPrefix("wi-component");
	public static final String INDICATOR_BASE_URI = PrefixManager.getURIPrefix("wi-indicator");
	public static final String MEASURE_BASE_URI =  PrefixManager.getURIPrefix("wi-measure");
	public static final String OBSERVATION_BASE_URI =  PrefixManager.getURIPrefix("wi-observation");
	public static final String AREA_BASE_URI =  PrefixManager.getURIPrefix("wi-area");
	public static final String COUNTRY_BASE_URI = PrefixManager.getURIPrefix("wi-country");
	public static final String REGION_BASE_URI = PrefixManager.getURIPrefix("wi-region");
	public static final String ONTOLOGY_BASE_URI = PrefixManager.getURIPrefix("wi-onto");
	public static final String ORGANIZATION_BASE_URI = PrefixManager.getURIPrefix("wi-org");
	public static final String SLICES_BASE_URI = PrefixManager.getURIPrefix("wi-slice");
	public static final String DATASET_BASE_URI = PrefixManager.getURIPrefix("wi-dataset");
	public static final String DSD_BASE_URI = PrefixManager.getURIPrefix("wi-dsd");
	public static final String RS_BASE_URI = PrefixManager.getURIPrefix("wi-rank");
	public static final String INDEX_BASE_URI = PrefixManager.getURIPrefix("wi-index");
	public static final String SCORE_BASE_URI = PrefixManager.getURIPrefix("wi-score");

	
	private DatasetTemplateDAO dao;
	private CountryDAO daoCountries;
	private ObservationDAO<ObservationTO> daoObservations;
	Map<String, Integer> counters;
	private Map<String, DatasetTO> datasetsInfo;
	private boolean primary;



	public TransformerAppServ(
			DatasetTemplateDAO dao,
			CountryDAO daoCountries,
			ObservationDAO<ObservationTO> daoObservations,
			Map<String, DatasetTO> datasetsInfo) {
		super();
		this.dao = dao;
		this.daoCountries = daoCountries;
		this.daoObservations = daoObservations;
		this.datasetsInfo = datasetsInfo;
	}

	protected static Logger logger = Logger.getLogger(TransformerAppServ.class);
	
	public Map<Dataset, CompleteModelTO> transformSecondaryIndicators(){
		this.primary = Boolean.FALSE;
		return transformIndicators();
	}

	private Map<Dataset, CompleteModelTO> transformIndicators() {
		logger.info("Transforming indicators");
		this.counters = new HashMap<String, Integer>();
		Map<Dataset, CompleteModelTO> observationMap = new HashMap<Dataset, CompleteModelTO>();
		List<Dataset> datasets = dao.getDatasets();
		for(Dataset dataset:datasets){
			//Init counters for each dataset
			logger.info("Transforming dataset");
			//String id = (this.primary?dataset.getId()+dataset.getIndicator().value():dataset.getId());
			String id = dataset.getId();
			this.counters.put(id, 0);
			logger.info("Counter for: "+id+" "+dataset.getIndicator());
			CompleteModelTO createRDFModelFromDataset = 
					createRDFModelFromDataset(dataset,daoObservations,daoCountries,this.datasetsInfo);
			observationMap.put(dataset, 
					createRDFModelFromDataset);
		}	
		for(String ds:this.counters.keySet()){
			logger.info("Dataset: "+ds+" number of observations "+this.counters.get(ds));
		}
		this.counters = null;
	    //Serialize
	    return observationMap;
	}

	public Map<Dataset, CompleteModelTO> transformPrimaryIndicators(){		
		this.primary = Boolean.TRUE;
		return transformIndicators();
	}
	
	public CompleteModelTO createRDFModelFromDataset(
			Dataset dataset,
			ObservationDAO<ObservationTO> daoObservations,
			CountryDAO daoCountries, Map<String, DatasetTO> datasetsInfo
			) {
		logger.info("Loading observations from indicator: "+dataset.getId()+" "+dataset.getIndicator());
		List<ObservationTO> observations = daoObservations.getObservationsByIndicator(dataset);
		logger.info("Decorating from indicator: "+dataset.getId());
		decorateCountries(daoCountries,observations);
		logger.info("Create RDF model from indicator: "+dataset.getId());
		return observationsAsRDF(observations,datasetsInfo, dataset);
	}
	
	public Model createRDFModelSlicesFromDataset(
			Dataset dataset
			) {
		Model model =ModelFactory.createDefaultModel();
		Resource datasetRDF = model.createResource(DATASET_BASE_URI+dataset.getId());
		Resource datasetDsdRDF = model.createResource(DSD_BASE_URI+dataset.getId());
		Resource qbDataset = model.createResource(HTTP_PURL_ORG_LINKED_DATA_CUBE+"Dataset");
		Resource qbSliceType = model.createResource(HTTP_PURL_ORG_LINKED_DATA_CUBE+"Slice");
		Property qbDSD = model.createProperty(HTTP_PURL_ORG_LINKED_DATA_CUBE+"structure");
		Property qbSlice = model.createProperty(HTTP_PURL_ORG_LINKED_DATA_CUBE+"slice");
		Property qbSliceStructure = model.createProperty(HTTP_PURL_ORG_LINKED_DATA_CUBE+"sliceStructure");
		Resource sliceStructure = model.createResource(DSD_BASE_URI+dataset.getId());
		model.add(datasetRDF,RDF.type,qbDataset);
		model.add(datasetRDF,qbDSD,datasetDsdRDF);
		for(TypeTable table:dataset.getTable()){
			for(TypeValues value:table.getValues()){			
				//1-Create slice
				String currentSliceURI = SLICES_BASE_URI+dataset.getId()+"/"+value.getYear().getYear();
				Resource currentSlice = model.createResource(currentSliceURI);
				model.add(currentSlice,RDF.type,qbSliceType);
				model.addLiteral(currentSlice,model.getProperty(ONTOLOGY_BASE_URI+"ref-year"),value.getYear().getYear());
				Resource indicator = model.createResource(INDICATOR_BASE_URI+dataset.getIndicator().value());
				model.add(currentSlice,model.getProperty(ONTOLOGY_BASE_URI+"ref-indicator"),indicator);
				model.add(currentSlice,qbSliceStructure,sliceStructure);
				//2-Add slice to dataset
				model.add(datasetRDF,qbSlice,model.createResource(currentSliceURI));				
			}			
		}
		return model;
	}
	
	private CompleteModelTO observationsAsRDF(List<ObservationTO> observations, Map<String, DatasetTO> datasetsInfo, Dataset currentDataset) {
		//this.counter = 0; Re-start for each indicator but not for each slice e.g.
		CompleteModelTO completeModel = new CompleteModelTO();
		Model model =ModelFactory.createDefaultModel();
		Model slices =createRDFModelSlicesFromDataset(currentDataset);
		addProperties(model);
		addProperties(slices);	
		
		Resource obs = model.createResource("http://purl.org/linked-data/cube#Observation");
		Resource weso = model.createResource(ORGANIZATION_BASE_URI+WESO_ACRONYM);
		Resource webfoundation = model.createResource(ORGANIZATION_BASE_URI+"WebFoundation");
		
		Resource lastResource = null;
		for(ObservationTO observation:observations){
			CountryTO countryDecorated = observation.getCountryDecorated();			
			String code = null;
			String countryName = null;
			Resource uriArea = null;
			if(countryDecorated == null){
				logger.warn("Fail country decorated value: "+countryDecorated+" with initial country "+observation.getCountry());
				code = encodeNotCountry(observation.getCountry()); //FIXME
				countryName = code;
				uriArea = model.createResource(TransformerAppServ.REGION_BASE_URI+code);
			}else{
				code = countryDecorated.getCode();
				countryName = countryDecorated.getName();
				uriArea = model.createResource(observation.getCountryDecorated().toURI());
			}
		
			String idIndicator= prettyId(observation);
			String idURI = getId(extracId(observation));
			String uri = OBSERVATION_BASE_URI 
						+ idIndicator
						+"/"
						+ idURI;
						//+ "/"
						//+ code+ "/"
						//+ observation.getYear()+
						//hackTitle(observation.getMeasureId());	
			
	
					
			Resource dataset = model.createResource(DATASET_BASE_URI+idIndicator);
			Resource indicator = model.createResource(INDICATOR_BASE_URI+hackId(idIndicator));	

			Resource observationRDF = model.createResource(uri);		
			
			Resource status = model.createResource(getStatus(observation));	
			model.add(observationRDF, RDF.type, obs);
			
			String title = datasetsInfo.get(observation.getDataset().getIndicator().value()).getIndicator();	
			
			model.add(observationRDF,RDFS.comment,literalLang(model,title,LANG_EN));
			model.add(observationRDF,RDFS.label,literalLang(model,title+" in "+prettyPrint(countryName)+" during "+observation.getYear(),LANG_EN));
			model.addLiteral(observationRDF,DCTerms.date,Integer.valueOf(observation.getYear()));
			//model.add(observationRDF,DCTerms.description,literalLang(model,title,LANG_EN));
			//model.add(observationRDF,DCTerms.identifier,idIndicator);
			model.add(observationRDF,DCTerms.publisher,webfoundation);
			model.add(observationRDF,DCTerms.contributor,weso);
			model.add(observationRDF,model.getProperty(ONTOLOGY_BASE_URI+"ref-indicator"),indicator);
			double value;
			String md5Hash;
			try{
				value = Double.valueOf(observation.getValue());
				model.addLiteral(observationRDF,model.getProperty(ONTOLOGY_BASE_URI+"value"),value);
				//MD5: indicator, type of dataset, country ISO-code-3, year, value, publisher
				md5Hash = hackId(idIndicator) +  MD5_SEPARATOR +
						hackIdType(observation.getDataset().getId()) + MD5_SEPARATOR +
						code + MD5_SEPARATOR +
						observation.getYear()+ MD5_SEPARATOR +
						observation.getValue() + MD5_SEPARATOR + WESO_ACRONYM;
			}catch(Exception e){
				logger.warn("Resource: "+uri+" exception converting string "+observation+" to number, set to MISSING VALUE.");
				status = model.createResource(HTTP_PURL_ORG_LINKED_DATA_SDMX_2009_CODE+StatusUtils.MISSING);		
				md5Hash = hackId(idIndicator) +  MD5_SEPARATOR +
						hackIdType(observation.getDataset().getId()) + MD5_SEPARATOR +
						code + MD5_SEPARATOR +
						observation.getYear() + MD5_SEPARATOR + WESO_ACRONYM; 			
			}
			
			model.addLiteral(observationRDF,model.getProperty(ONTOLOGY_BASE_URI+"ref-year"),Integer.valueOf(observation.getYear()));
			model.add(observationRDF,model.getProperty(ONTOLOGY_BASE_URI+"ref-area"),uriArea);
			model.add(observationRDF,model.getProperty(HTTP_PURL_ORG_LINKED_DATA_SDMX_2009_CONCEPT+"obsStatus"),status);
			model.add(observationRDF,model.getProperty(HTTP_PURL_ORG_LINKED_DATA_CUBE+"dataSet"),dataset);			
			model.add(observationRDF,model.getProperty(ONTOLOGY_BASE_URI+"sheet-type"),
			model.createResource(ONTOLOGY_BASE_URI+hackIdType(observation.getDataset().getId())));
			model.add(observationRDF,model.getProperty(ONTOLOGY_BASE_URI+"md5-checksum"),MD5Utils.generate(md5Hash));
					
		
			//Finally, add observation to slice
			Resource slice =model.getResource(SLICES_BASE_URI+(observation.getDataset().getId())+"/"+observation.getYear()); 
			slices.add(slice, model.getProperty(HTTP_PURL_ORG_LINKED_DATA_CUBE+"observation"), observationRDF);
			
	
			
		}
		completeModel.setObservations(model);
		completeModel.setSlices(slices);
		
		return completeModel;
	}

	public static String getStatus(ObservationTO observation) {
		if(observation.getStatus().equals(StatusUtils.NORMALISED)){
			return ONTOLOGY_BASE_URI+observation.getStatus();
		}
		else return HTTP_PURL_ORG_LINKED_DATA_SDMX_2009_CODE+observation.getStatus();
	}

	public static Literal literalLang(Model m, String value, String lang){
		return m.createLiteral(value, lang);
	}
	private String extracId(ObservationTO observation) {
		return observation.getDataset().getId();
	}

	private String prettyId(ObservationTO observation) {
		return observation.getDataset().getId();
	}
	
	private String hackId(String id) {
		String []ids = id.split("-");
		return (ids.length>1?ids[0]:id);		
	}
	

	private String hackIdType(String id) {
		String []ids = id.split("-");
		return (ids.length>1?ids[1]:id);		
	}

	private String getId(String idRaw) {
		int value = this.counters.get(idRaw);
		this.counters.put(idRaw, value+1);
		return "obs"+(value+1);
	}

	static String prettyPrint(String countryName) {
		StringBuffer sb = new StringBuffer();
		String[] names = countryName.split(",");
		for(int i = names.length-1; i>=0; i--){
			sb.append(names[i].trim());
			if( i>0){
				sb.append(", ");
			}
		}
		return sb.toString();
	}

	private String hackTitle(String measureId) {
		if (measureId != null && measureId.equals("NotProvided")){
			return "";
		}else return "/"+measureId;
	}

	private void addProperties(Model model) {
		model.createProperty(ONTOLOGY_BASE_URI+"value");
		model.createProperty(ONTOLOGY_BASE_URI+"ref-area");
		model.createProperty(ONTOLOGY_BASE_URI+"ref-year");
		model.createProperty(ONTOLOGY_BASE_URI+"ref-indicator");
		model.createProperty(HTTP_PURL_ORG_LINKED_DATA_SDMX_2009_ATTRIBUTE,"unitMeasure");
		model.createProperty(HTTP_PURL_ORG_LINKED_DATA_SDMX_2009_CONCEPT,"obsStatus");
		model.createProperty(HTTP_PURL_ORG_LINKED_DATA_CUBE,"dataSet");
		
	}

	public static String encodeNotCountry(String country) {		
		return country.replaceAll(" ", "_");
	}

	protected static void decorateCountries(CountryDAO daoCountries, List<ObservationTO> observations){
		List<CountryTO> countries = daoCountries.getCountries();
		CountryReconciliator reconciliatorCountry = new CountryReconciliator(countries);	
	    for(ObservationTO observation:observations){
	    	List<CountryTO> results = reconciliatorCountry.search(observation);
	    	if(results.size()>0){
	    		CountryTO match = results.get(0);
	    		//logger.debug("Match for country name "+observation.getCountry()+" found "+match.getName());	    		
	    		observation.setCountryDecorated(match);
	    	}else{
	    		logger.warn("No match country for observation "+observation);
	    	}	    	
	    }	    	
	  }

	
	protected static void decorateCountriesWB(CountryDAO daoCountries, List<ObservationTO> observations){
		List<CountryTO> countries = daoCountries.getCountries();
		CountryReconciliator reconciliatorCountry = new CountryReconciliator(countries);	
	    for(ObservationTO observation:observations){
	    	List<CountryTO> results = reconciliatorCountry.searchToWB(observation);
	    	if(results.size()>0){
	    		CountryTO match = results.get(0);
	    		//logger.debug("Match for country name "+observation.getCountry()+" found "+match.getName());	    		
	    		observation.setCountryDecorated(match);
	    	}else{
	    		logger.warn("No match country for observation "+observation);
	    	}	    	
	    }	    	
	  }
	public Map<Dataset, CompleteModelTO> transform(boolean isPrimary) {
		this.primary = isPrimary;
		return transformIndicators();
	}
	

}
